package services;

import gestures.Gesture;

import java.io.*;
import java.net.*;
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.*;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import com.rabbitmq.client.*;

/**
 * Client that communicates and listens for gesture events with the gesture
 * recognition service
 * 
 * @author PDAY (The MITRE Corporation)
 * 
 */
public class GestureClient {


	// List of registered gesture listeners
	private List<ListenerRegistration> registeredListeners;


	private HashMap<Integer, Gesture> gestureMap;
    private HashMap<Integer, Gesture> fiducialMap;

   private Connection conn;
   private Channel channel;
   private Channel objectChannel;
   String serverAddress;// = "af75654-win";
   String exchange;// = "grip";
   String queue;// = "grip-gestures";
   String objectQ;// = "grip-objects";
   String exchangeObject;// = "grip-fiducial";
   String routingKey;// = "";
   QueueingConsumer consumer;
   QueueingConsumer consumerObjects;
   
   private String configFile;



	
	/**
	 * Create a new instance of the listening client
	 * 
	 * @param nickname
	 *            Location of the service that gestures will register with
	 * @throws IOException 
	 */
	 public GestureClient(String nickname, String configFile) throws IOException
        {
		 	this.configFile = configFile;
		 	if(configFile != null)
		 		parseConfigSettings();
		 	
            try{
            this.gestureMap = new HashMap<Integer, Gesture>();
            fiducialMap = new HashMap<Integer, Gesture>();

            this.registeredListeners = new ArrayList<ListenerRegistration>();
            ConnectionFactory factory = new ConnectionFactory();

            factory.setHost(serverAddress);
            factory.setPort(5672);
            conn = factory.newConnection();
            channel = conn.createChannel();
            channel.exchangeDeclare(exchange, "fanout");
            channel.queueDeclare(queue + nickname, false, false, true, null);
            channel.queueBind(queue + nickname, exchange, routingKey);

            objectChannel = conn.createChannel();

            objectChannel.exchangeDeclare(exchangeObject, "fanout");
            objectChannel.queueDeclare(objectQ + nickname,false,false,true,null);
            objectChannel.queueBind(objectQ + nickname, exchangeObject, routingKey);



            consumer = new QueueingConsumer(channel);
            channel.basicConsume(queue + nickname, false, consumer);



            Thread gestureListener = new Thread(new ListenerThread(consumer,this));
            gestureListener.start();


            consumerObjects = new QueueingConsumer(objectChannel);


            // At this point, messages will be being asynchronously delivered,
            // and will be queueing up in consumer.Queue.

            //Thread objectListener = new Thread(new ListenerThread(consumerObjects,this));
            //objectListener.start();


            }catch (IOException e){
               e.printStackTrace();
            }

           // serv = new HTTP.HTTPServer(this, portForCallback);
           // serv.startListening();

        }

	 public void parseConfigSettings() throws IOException
	 {
		 BufferedReader reader = new BufferedReader(new FileReader(configFile));
		 
         Hashtable<String, String> settingLookup = new Hashtable<String, String>();
         String line = reader.readLine();
         while (line != null)
         {
             String[] elements = line.split(":");
             if (elements.length > 1)
                 settingLookup.put(elements[0], elements[1].trim());
             line = reader.readLine();
         }

         serverAddress = settingLookup.get("serverAddress");
         exchange = settingLookup.get("exchange");
         exchangeObject = settingLookup.get("exchangeObject");
         queue = settingLookup.get("queue");
         objectQ = settingLookup.get("objectQ");
         routingKey = settingLookup.get("routingKey");

         reader.close();
 }

	/**
	 * Register a new gesture listener with the library
	 * 
	 * @param listener
	 *            Gesture listener to add
	 */


	public void registerListener(ListenerRegistration listener)
    {
       this.registeredListeners.add(listener);

    }

    /// <summary>
    /// Remove a listener from getting gesture updates
    /// </summary>
    /// <param name="listener">Listener to remove</param>
    public void unRegisterListener(ListenerRegistration listener)
    {
        this.registeredListeners.remove(listener);


    }
	/**
	 * Handles an incoming gesture that occurred. Notifies any registered
	 * listeners that care about this gesture
	 * 
	 * @param g
	 *            Gesture that occurred
	 */
	public void gestureOccurred(Gesture g) {
		if (this.gestureMap.containsKey(g.getID())) {
			Gesture prevGest = this.gestureMap.get(g.getID());
			g.setPreviousGesture(prevGest);
		}
		this.gestureMap.put(g.getID(), g);
		
		for (int i = 0; i < this.registeredListeners.size(); i++) {
			ListenerRegistration lr = this.registeredListeners.get(i);
			if (lr.caresAboutGesture(g)) {
				lr.getListenerGesture().actionPerformed(this,
						new services.subscribers.GestureEventArgs(g));
			}
			else {
				//System.out.println( lr.getListenerName() + " does not care about gesture " + g.getClass().getName() );
			}
		}
	}
}

/**
 * Listening thread server that will handle incoming HTTP POSTS by the gesture
 * recon service
 * 
 * @author PDAY (The MITRE Corporation)
 * 
 */
class ListenerThread implements Runnable {

	private QueueingConsumer consumer;
    public Boolean listenOnQ = true;
    private GestureClient gl;
	ListenerThread(QueueingConsumer consumer, GestureClient gl) {
		this.consumer = consumer;
        this.gl=gl;
	}

    public void run(){

       while (listenOnQ)
            {
                try
                {
                    QueueingConsumer.Delivery delivery;
                    try {
                        //System.out.println("waiting");
                        delivery = consumer.nextDelivery();
                        //System.out.println("got something");
                        handle(delivery.getBody());
                    } catch (InterruptedException ie) {
                        continue;
                    }
                    //System.out.println("acking");
                    consumer.getChannel().basicAck(delivery.getEnvelope().getDeliveryTag(), false);




                }
                catch (IOException ex)
                {
                    //System.out.println("broke");
                    // The consumer was cancelled, the model closed, or the
                    // connection went away.
                    break;
                }
            }
    }


	public void handle(byte[] body) {

		boolean parseSuccess = false;



		// Check to see if there's content, otherwise throw a 400


			// Read the content
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db;
			Document doc;

			try {
				db = dbf.newDocumentBuilder();
				doc = db.parse(new ByteArrayInputStream(body));

				Gesture[] gestures = GestureParser.parseGestures(doc);



				if (gestures != null) {
					//System.out.println("Parse successful. Sending success");
					//System.out.println("Total gestures: " + gestures.length);
					parseSuccess = true;

					for (Gesture gesture : gestures) {
						gl.gestureOccurred(gesture);
					}

				}


			} catch (ParserConfigurationException e) {
				e.printStackTrace();
				parseSuccess = false;
			} catch (SAXException e) {
				e.printStackTrace();
				parseSuccess = false;
			} catch (NumberFormatException e) {
				e.printStackTrace();
				parseSuccess = false;
			} catch (IOException e){

            }
		}


}
